#####################################################################
# Product:      KnowledgeEngine
#
# File:         KERL/Engine.py
#
# Copyright:    Copyright (c) 2007, Faulkner Technologies
#
# Author:       James Davies
#
# Description:  The New KERL Compiler
#               
#               Compiles KERL Statements into an internal format which can then be optimized, executed, or compiled
#               into SQL, Javascript, or back into KERL
#
#####################################################################

from Products.KnowledgeEngine.KERL.Tokens import AssessmentToken, ComponentToken, StringToken, ReferenceToken, UserToken, NumericToken, SearchIndexToken, PairToken, ModelToken
from Products.KnowledgeEngine.KERL.Operators import *
from Products.KnowledgeEngine.KERL.CompiledKERL import CompiledKERL, OptimizedKERL
from Products.KnowledgeEngine.KERL.KERLExceptions import *

# hack - just use old exceptions for now
#from Products.KnowledgeEngine.KERLProcessor import ComponentNotFound, InvalidKerlExpression

groupOperators = {'all':  AllOperator,
                  'any':  AnyOperator,
                  'none': NoneOperator }

def parseRHS( svalue, namespace ):
    """ Parse Right-Hand-Side expression """

    if svalue is None:
        value = None
    elif type(svalue) is str:
        value = StringToken.StringToken( svalue, namespace=namespace )
    elif type(svalue) in (int, float):
        value = NumericToken.NumericToken( svalue, namespace=namespace )
    elif type(svalue) is tuple and len(svalue) == 2:
        value = PairToken.PairToken( svalue, namespace=namespace )
    else:
        raise UnexpectedResult, svalue
        
    return value
                    

class Engine:
    
    __allow_access_to_unprotected_subobjects__ = 1
    
    cache = {}
    
    @classmethod
    def getInstance( cls ):
        
        if not hasattr( cls, '_instance'):
            cls._instance = Engine()
        
        return cls._instance
        
        
    def compile(self, expression, model, component, assessment=None, section=None, **kw):
                        
        #print ". " + `expression`
                        
        result = self.do_compile( expression, model, component, **kw )
        
        if assessment and section:
            return OptimizedKERL( result )
        else:
            return CompiledKERL( result )
        
    
    def do_compile(self, expression, model, component, assessment=None, section=None, lazyDependancies=False, depth=0):
        
        
        #print "Compiling: %s" % `expression`
        
        namespace = {'component': component, 'model': model} 
               
        # ---------------------------------------------------------------------------------
        # Handle Expression Groups
        if type(expression) is list and expression[0] in ('any','all','none'):
            
            soperator = expression[0]
            
            # Determine which group operator to use (All, Any, None)
            Klass = groupOperators[ soperator ]
                                                 
            # Compile the sub expressions
            arguments = []
            for clause in expression[1:]:                                
                arguments.append(self.do_compile(clause, model, component, assessment=assessment, section=section, lazyDependancies=lazyDependancies, depth=depth+1 ))
                
            # Pass to compiled result to the operator
            operator = Klass( *arguments )

            return operator
        
        # ---------------------------------------------------------------------------------
        # Handle individual expressions        
        elif type(expression) is list or type(expression) is tuple:
                       
            
            try:
                chash = tuple(expression) + (lazyDependancies,)
                if chash in self.cache:
                    #print "Cache Hit"
                    #return self.cache[chash]
                    pass     
                else:
                    pass
                    #print "Cache Miss"
            except TypeError: # Uncachable
                pass
                       
                       
            # Break apart the expression into token, operator, value
            if len(expression) == 3:
                stoken, soperator, svalue = expression
            elif len(expression) == 2:
                stoken, soperator = expression
                svalue = None
            elif len(expression) == 4:
                stoken, soperator, svalue1, svalue2 = expression
                svalue = ( svalue1, svalue2 )
                
            # Hack
            
            #print stoken
            
            if type(stoken) is list:
                token = self.do_compile( stoken, model, component, lazyDependancies=lazyDependancies, depth=depth+1)
                
            elif stoken == "reference":
                token = ReferenceToken.ReferenceToken( namespace )
            
            
            elif stoken == "assessment":
                token = AssessmentToken.AssessmentToken( namespace=namespace )
                                          
            elif stoken == "currentuser":
                token = UserToken.CurrentUserToken( namespace=namespace )
                     
            elif stoken == "model":
                token = ModelToken.ModelToken( model, namespace=namespace ) 
                     
            elif stoken.startswith('idx:'):
                searchIndex = stoken[4:]                     
                token = SearchIndexToken.SearchIndexToken( searchIndex, namespace=namespace )
                
            
                    
            else: # If all else fails, assume component operator
                            
                if stoken == "component":
                    mycomponent = namespace['component']                              # Representing the current component               
                else:
                    mycomponent = namespace['model'].getComponentByIdentity( stoken ) # Representing a specific component
                                        
                if not mycomponent:
                    raise ComponentNotFound, "Could not find component: %s" % stoken
                                        
                #if lazyDependancies and depth > 2:
                    #print "Returning ISVISIBLE"
                    #return IsVisible( mycomponent )
                                        
                token = mycomponent.getKERLTokenClass()( mycomponent, namespace=namespace )
                    
                
                    
                # Calculate the operator and value tokens
                operator = token.findOperator( soperator )
                value = parseRHS( svalue, namespace=namespace )           
                                        
                componentResult = operator( token, value )
                                         
                
                    
                # Process dependancies
                showIf = []
                
                if (lazyDependancies and depth >= 1 ):
                    result = AllOperator( componentResult, IsVisible( mycomponent ) )
                else:
                    componentShowIf = mycomponent.getShowIfKERL()
                    if componentShowIf: showIf.append( componentShowIf )
                    
                    sections       = mycomponent.getParents()
                    for mysection in sections:
                        mykerl = mysection.getShowIfKERL()
                        if mykerl: showIf.append( mykerl )
                                    
                    # If the target component has a showIf statement, we only evaluate to True if the component is visible
                    # Thus, we need to wrap this token and its dependancies in an 'Any' operator
                    if showIf:
                                                                                    
                        # Compile the showif
                        compiledShowIf = self.do_compile( ['all'] + showIf, model, mycomponent, lazyDependancies=lazyDependancies, depth=depth+1 ) 
                        
                        # Combine our expression with the showif expression, using an 'All' operator
                        result =  AllOperator( componentResult, compiledShowIf )                                               
                    else:
                        result = componentResult


                if assessment and section:
                    result = result.optimize( assessment, section )

                try:
                    self.cache[ chash ] = result                    
                except TypeError:
                    pass
                
                return result
            
            # ------------------------------------------------------------------------------------------------
            
            value = parseRHS( svalue, namespace )
            
            #if svalue is None:
                #value = None
            #elif type(svalue) is str:
                #value = StringToken.StringToken( svalue, namespace=namespace )
            #elif type(svalue) in (int, float):
                #value = NumericToken.NumericToken( svalue, namespace=namespace )
                
            #value = StringToken.StringToken( svalue, namespace=namespace )
            
            #print "SOperator: " + `soperator`
                
            #print expression
            operator = token.findOperator( soperator )
            
            result = operator(token, value )
            
            if( assessment and section ):
                result = result.optimize( assessment, section )
            
            try:
                self.cache[ chash ] = result
            except TypeError:
                pass
            
            return result
                      
        else:
            # TODO - Handle unknown values
            raise InvalidKerlExpression, expression
            #compiled = expression
            
            
        #return CompiledKERL( compiled )